#!/usr/bin/env python3
"""Helper script to bump the current version."""

import argparse
from pathlib import Path
import re
import subprocess

from packaging.version import Version

from homeassistant import const
from homeassistant.util import dt as dt_util


def _bump_release(release, bump_type):
    """Bump a release tuple consisting of 3 numbers."""
    major, minor, patch = release

    if bump_type == "patch":
        patch += 1
    elif bump_type == "minor":
        minor += 1
        patch = 0

    return major, minor, patch


def bump_version(
    version: Version, bump_type: str, *, nightly_version: str | None = None
) -> Version:
    """Return a new version given a current version and action."""
    to_change = {}

    if bump_type == "minor":
        # Convert 0.67.3 to 0.68.0
        # Convert 0.67.3.b5 to 0.68.0
        # Convert 0.67.3.dev0 to 0.68.0
        # Convert 0.67.0.b5 to 0.67.0
        # Convert 0.67.0.dev0 to 0.67.0
        to_change["dev"] = None
        to_change["pre"] = None

        if not version.is_prerelease or version.release[2] != 0:
            to_change["release"] = _bump_release(version.release, "minor")

    elif bump_type == "patch":
        # Convert 0.67.3 to 0.67.4
        # Convert 0.67.3.b5 to 0.67.3
        # Convert 0.67.3.dev0 to 0.67.3
        to_change["dev"] = None
        to_change["pre"] = None

        if not version.is_prerelease:
            to_change["release"] = _bump_release(version.release, "patch")

    elif bump_type == "dev":
        # Convert 0.67.3 to 0.67.4.dev0
        # Convert 0.67.3.b5 to 0.67.4.dev0
        # Convert 0.67.3.dev0 to 0.67.3.dev1
        if version.is_devrelease:
            to_change["dev"] = ("dev", version.dev + 1)
        else:
            to_change["pre"] = ("dev", 0)
            to_change["release"] = _bump_release(version.release, "minor")

    elif bump_type == "beta":
        # Convert 0.67.5 to 0.67.6b0
        # Convert 0.67.0.dev0 to 0.67.0b0
        # Convert 0.67.5.b4 to 0.67.5b5

        if version.is_devrelease:
            to_change["dev"] = None
            to_change["pre"] = ("b", 0)

        elif version.is_prerelease:
            if version.pre[0] == "a":
                to_change["pre"] = ("b", 0)
            if version.pre[0] == "b":
                to_change["pre"] = ("b", version.pre[1] + 1)
            else:
                to_change["pre"] = ("b", 0)
                to_change["release"] = _bump_release(version.release, "patch")

        else:
            to_change["release"] = _bump_release(version.release, "patch")
            to_change["pre"] = ("b", 0)

    elif bump_type == "nightly":
        # Convert 0.70.0d0 to 0.70.0d201904241254, fails when run on non dev release
        if not version.is_devrelease:
            raise ValueError("Can only be run on dev release")

        new_dev = dt_util.utcnow().strftime("%Y%m%d%H%M")
        if nightly_version:
            new_version = Version(nightly_version)
            if new_version.release != version.release:
                raise ValueError("Nightly version must have the same release version")
            if not new_version.is_devrelease:
                raise ValueError("Nightly version must be a dev version")
            new_dev = new_version.dev

        to_change["dev"] = ("dev", new_dev)

    else:
        raise ValueError(f"Unsupported type: {bump_type}")

    temp = Version("0")
    temp._version = version._version._replace(**to_change)  # noqa: SLF001
    return Version(str(temp))


def write_version(version):
    """Update Home Assistant constant file with new version."""
    content = Path("homeassistant/const.py").read_text()

    major, minor, patch = str(version).split(".", 2)

    content = re.sub(
        "MAJOR_VERSION: Final = .*\n", f"MAJOR_VERSION: Final = {major}\n", content
    )
    content = re.sub(
        "MINOR_VERSION: Final = .*\n", f"MINOR_VERSION: Final = {minor}\n", content
    )
    content = re.sub(
        "PATCH_VERSION: Final = .*\n", f'PATCH_VERSION: Final = "{patch}"\n', content
    )

    Path("homeassistant/const.py").write_text(content)


def write_version_metadata(version: Version) -> None:
    """Update pyproject.toml file with new version."""
    content = Path("pyproject.toml").read_text(encoding="utf8")

    content = re.sub(r"(version\W+=\W).+\n", f'\\g<1>"{version}"\n', content, count=1)

    Path("pyproject.toml").write_text(content, encoding="utf8")


def write_ci_workflow(version: Version) -> None:
    """Update ci workflow with new version."""
    content = Path(".github/workflows/ci.yaml").read_text()

    short_version = ".".join(str(version).split(".", maxsplit=2)[:2])
    content = re.sub(
        r"(\n\W+HA_SHORT_VERSION: )\"\d{4}\.\d{1,2}\"\n",
        f'\\g<1>"{short_version}"\n',
        content,
        count=1,
    )

    Path(".github/workflows/ci.yaml").write_text(content)


def main() -> None:
    """Execute script."""
    parser = argparse.ArgumentParser(description="Bump version of Home Assistant")
    parser.add_argument(
        "type",
        help="The type of the bump the version to.",
        choices=["beta", "dev", "patch", "minor", "nightly"],
    )
    parser.add_argument(
        "--commit", action="store_true", help="Create a version bump commit."
    )
    parser.add_argument(
        "--set-nightly-version", help="Set the nightly version to", type=str
    )

    arguments = parser.parse_args()

    if arguments.set_nightly_version and arguments.type != "nightly":
        parser.error("--set-nightly-version requires type set to nightly.")

    if (
        arguments.commit
        and subprocess.run(["git", "diff", "--quiet"], check=False).returncode == 1
    ):
        print("Cannot use --commit because git is dirty.")
        return

    current = Version(const.__version__)
    bumped = bump_version(
        current, arguments.type, nightly_version=arguments.set_nightly_version
    )
    assert bumped > current, "BUG! New version is not newer than old version"

    write_version(bumped)
    write_version_metadata(bumped)
    write_ci_workflow(bumped)
    print(bumped)

    if not arguments.commit:
        return

    subprocess.run(["git", "commit", "-nam", f"Bump version to {bumped}"], check=True)


def test_bump_version() -> None:
    """Make sure it all works."""
    import pytest

    assert bump_version(Version("0.56.0"), "beta") == Version("0.56.1b0")
    assert bump_version(Version("0.56.0b3"), "beta") == Version("0.56.0b4")
    assert bump_version(Version("0.56.0.dev0"), "beta") == Version("0.56.0b0")

    assert bump_version(Version("0.56.3"), "dev") == Version("0.57.0.dev0")
    assert bump_version(Version("0.56.0b3"), "dev") == Version("0.57.0.dev0")
    assert bump_version(Version("0.56.0.dev0"), "dev") == Version("0.56.0.dev1")

    assert bump_version(Version("0.56.3"), "patch") == Version("0.56.4")
    assert bump_version(Version("0.56.3.b3"), "patch") == Version("0.56.3")
    assert bump_version(Version("0.56.0.dev0"), "patch") == Version("0.56.0")

    assert bump_version(Version("0.56.0"), "minor") == Version("0.57.0")
    assert bump_version(Version("0.56.3"), "minor") == Version("0.57.0")
    assert bump_version(Version("0.56.0.b3"), "minor") == Version("0.56.0")
    assert bump_version(Version("0.56.3.b3"), "minor") == Version("0.57.0")
    assert bump_version(Version("0.56.0.dev0"), "minor") == Version("0.56.0")
    assert bump_version(Version("0.56.2.dev0"), "minor") == Version("0.57.0")

    now = dt_util.utcnow().strftime("%Y%m%d%H%M")
    assert bump_version(Version("0.56.0.dev0"), "nightly") == Version(
        f"0.56.0.dev{now}"
    )
    assert bump_version(
        Version("2024.4.0.dev20240327"),
        "nightly",
        nightly_version="2024.4.0.dev202403271315",
    ) == Version("2024.4.0.dev202403271315")
    with pytest.raises(ValueError, match="Can only be run on dev release"):
        bump_version(Version("0.56.0"), "nightly")
    with pytest.raises(
        ValueError, match="Nightly version must have the same release version"
    ):
        bump_version(
            Version("0.56.0.dev0"),
            "nightly",
            nightly_version="2024.4.0.dev202403271315",
        )
    with pytest.raises(ValueError, match="Nightly version must be a dev version"):
        bump_version(Version("0.56.0.dev0"), "nightly", nightly_version="0.56.0")


if __name__ == "__main__":
    main()
